package it.aqquadro.arex;

import it.globalbase.tool.AppProperties;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class ArexFilter implements Filter {

	public static final String AREX_ENDPOINT_PARAM = "arexEndPoint";

	public static final String REXY_ATTRIBUTE = "rexy";

	public static final String ALIAS_PROPERTIES = "it/aqquadro/arex/arex-alias.properties";

	private FilterConfig filterConfig;

	private String arexEndPoint;

	private static final Logger log = Logger.getLogger(ArexFilter.class.getName());

	public static final String ALIAS_ATTRIBUTE = "alias";

	public static final String SEARCH = "/search";

	public ArexFilter() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

		if (request instanceof HttpServletRequest) {
			HttpServletRequest req = (HttpServletRequest) request;

			String requestURI = req.getRequestURI();
			log.info("requestURI: " + requestURI);

			if (requestURI == null) {
				// non gestito, continua la chain normalmente
				filterChain.doFilter(request, response);
				return;

			} else {
				// cerco gli alias

				String aliasKey = null, cleanRequestURI = null;

				// nel caso la richiesta sia dritta verso arex rimuovo l'endpoint
				cleanRequestURI = (requestURI.startsWith(arexEndPoint)) ? "/" + requestURI.replaceFirst(arexEndPoint, "") : requestURI;

				if (!requestURI.startsWith(arexEndPoint)) {
					// cerco alias solo se la richiesta non è "dritta" verso arex

					try {
						/**
						 * cerco i possibili alias
						 */

						aliasKey = searchAlias(cleanRequestURI);
						if (aliasKey != null) {
							String alias = AppProperties.get(ALIAS_PROPERTIES, aliasKey);

							log.info("Trovato alias per '" + cleanRequestURI + "': " + alias);
							cleanRequestURI = alias;
						}
					} catch (IOException e) {
						log.severe("Errore nella lettura del file alias: " + e.getMessage());
					}
				}

				// recupero il nome della classe
				String arexClassPath = getActionClassName(cleanRequestURI);
				log.info("arexClassPath: " + arexClassPath);

				Class<?> arexClass = null;

				try {
					arexClass = Class.forName(arexClassPath);
				} catch (ClassNotFoundException e) {
					log.warning("Nessuna trovata per la path: " + arexClassPath);
				}

				if (arexClass == null) {
					filterChain.doFilter(request, response);
					return;
				} else {
					Constructor<?> ct = null;

					try {
						ct = arexClass.getConstructor();
					} catch (SecurityException e) {
						log.warning("SecurityException: " + e.getMessage());
					} catch (NoSuchMethodException e) {
						log.warning("NoSuchMethodException: " + e.getMessage());
					}

					if (ct == null) {
						log.warning("Errori durante il recupero del costruttore, non sarà utilizzato arex");
						filterChain.doFilter(request, response);
						return;

					} else {
						Object instance = null;

						try {
							instance = ct.newInstance();
						} catch (IllegalArgumentException e) {
							log.warning("IllegalArgumentException: " + e.getMessage());
						} catch (InstantiationException e) {
							log.warning("InstantiationException: " + e.getMessage());
						} catch (IllegalAccessException e) {
							log.warning("IllegalAccessException: " + e.getMessage());
						} catch (InvocationTargetException e) {
							log.warning("InvocationTargetException: " + e.getMessage());
						}

						if (instance == null) {
							log.warning("Errori durante la creazione dell'istanza, non sarà utilizzato arex");
							filterChain.doFilter(request, response);
							return;
						} else {
							if (instance instanceof ArexMouth) {
								ArexMouth am = (ArexMouth) instance;
								req.setAttribute(REXY_ATTRIBUTE, am);
								req.setAttribute(ALIAS_ATTRIBUTE, aliasKey);

								if (requestURI.startsWith(arexEndPoint)) {
									// Se la richiesta contiene già l'arexEndPoint non faccio il forward perchè l'url è già esatto
									filterChain.doFilter(request, response);
									return;
								} else {
									// eseguo il forward verso l'esatto url
									String forwardTo = arexEndPoint + requestURI.substring(1);
									req.getRequestDispatcher(forwardTo).forward(req, response);
									return;
								}

							} else {
								log.warning("L'istanza generata non implementa ArexMouth, non sarà utilizzato arex");
								filterChain.doFilter(request, response);
								return;
							}
						}
					}
				}
			}
		}
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.filterConfig = filterConfig;
		this.arexEndPoint = filterConfig.getServletContext().getInitParameter(AREX_ENDPOINT_PARAM);
		log.info("arexEndPoint: " + arexEndPoint);
	}

	public FilterConfig getFilterConfig() {
		return filterConfig;
	}

	/**
	 * Trasforma un URI web in full class name
	 * 
	 * @param requestURI
	 * @return
	 */
	private String getActionClassName(String requestURI) {
		return requestURI.substring(1).replace("/", ".");
	}

	/**
	 * Ricerca all'interno del file degli alias un papabile alias relativo all'URI della richiesta, effettua una "LEFT LIKE"
	 * 
	 * @param requestURI
	 * @return il valore dell'alias oppure null
	 * @throws IOException
	 *             nel caso di problemi durante la lettura del file di properties
	 */
	public static String searchAlias(String requestURI) throws IOException {
		Properties p = AppProperties.loadProperties(ALIAS_PROPERTIES);

		if (p.getProperty(requestURI) != null) {
			return requestURI;
		} else {
			Set<Object> pKeySet = p.keySet();
			Iterator<Object> it = pKeySet.iterator();

			while (it.hasNext()) {
				Object key = it.next();
				if (requestURI.startsWith(String.valueOf(key)) && !String.valueOf(key).equalsIgnoreCase("/")) {
					return String.valueOf(key);
				}
			}
		}

		return null;
	}
}
